/*
 * Copyright (C) 2016  Nicola Spanti (RyDroid) <dev@nicola-spanti.info>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */


#include <check.h>
#include "PlanetWars2dRT-core/specific/model/planet.h"
#include "PlanetWars2dRT-core/specific/controller/planet_collision.h"
#include "PlanetWars2dRT-core/specific/controller/planet_produce.h"
#include "PlanetWars2dRT-core/specific/model/ship_default.h"
#include "PlanetWars2dRT-core/specific/model/ship_power.h"
#include "check_observable_generic.h"


struct planet_t   a_planet;
struct observer_t an_observer;

void
tests_setup_init(void)
{
  planet_init_first_time_only_default(&a_planet);
  observer_init_first_time_only_unsafe(&an_observer);
}

void
tests_setup_free(void)
{
  planet_destructor(&a_planet);
  observer_destructor_unsafe(&an_observer);
}


bool
planet_add_observer_generic(void* a_planet,
			    const observer_t* an_observer)
{
  return planet_add_observer(a_planet, an_observer);
}

void
planet_remove_observers_generic(void* a_planet)
{
  planet_remove_observers(a_planet);
}

void
planet_notify_observers_generic(void* a_planet, void* data)
{
  planet_notify_observers(a_planet, data);
}

size_t
planet_get_nb_observers_generic(void* a_planet)
{
  return planet_get_nb_observers(a_planet);
}

START_TEST(test_planet_add_observer)
{
  test_add_observer_generic(&a_planet,
			    planet_add_observer_generic);
}
END_TEST

START_TEST(test_planet_remove_observers)
{
  test_remove_observers_generic(&a_planet,
				planet_add_observer_generic,
				planet_remove_observers_generic,
				planet_get_nb_observers_generic);
}
END_TEST

START_TEST(test_planet_notify_observers)
{
  test_notify_observers_generic(&a_planet,
				planet_add_observer_generic,
				planet_notify_observers_generic);
}
END_TEST


void
planet_observer_on_life_change_counter_handler(void* an_observable,
					     void* observer_data,
					     void* event_data)
{
  if(an_observable != NULL && event_data != NULL)
    {
      const observer_data_with_code* data
	= (observer_data_with_code*) event_data;
      if(data->code == PLANET_OBSERVER_CODE_LIFE_CHANGE)
	{
	  ++(*((unsigned int*) observer_data));
	}
    }
}

void
planet_observer_on_player_change_counter_handler(void* an_observable,
					     void* observer_data,
					     void* event_data)
{
  if(an_observable != NULL && event_data != NULL)
    {
      const observer_data_with_code* data
	= (observer_data_with_code*) event_data;
      if(data->code == PLANET_OBSERVER_CODE_PLAYER_CHANGE)
	{
	  ++(*((unsigned int*) observer_data));
	}
    }
}

void
planet_observer_on_ship_creation_counter_handler(void* an_observable,
					     void* observer_data,
					     void* event_data)
{
  if(an_observable != NULL && event_data != NULL)
    {
      const observer_data_with_code* data
	= (observer_data_with_code*) event_data;
      if(data->code == PLANET_OBSERVER_CODE_SHIP_CREATED)
	{
	  ++(*((unsigned int*) observer_data));
	}
    }
}

START_TEST(test_planet_event_on_life_change)
{
  unsigned int counter = 0;
  
  an_observer.handler = planet_observer_on_life_change_counter_handler;
  an_observer.data = &counter;
  an_observer.data_free = NULL;
  planet_add_observer(&a_planet, &an_observer);
  ck_assert_uint_eq(counter, 0);
  
  planet_reduce_life(&a_planet, 0);
  ck_assert_uint_eq(counter, 0);
  planet_reduce_life(&a_planet, 0);
  ck_assert_uint_eq(counter, 0);
  
  planet_reduce_life(&a_planet, 1);
  ck_assert_uint_eq(counter, 1);
}
END_TEST

START_TEST(test_planet_event_on_player_change)
{
  unsigned int counter = 0;
  
  an_observer.handler = planet_observer_on_player_change_counter_handler;
  an_observer.data = &counter;
  an_observer.data_free = NULL;
  planet_add_observer(&a_planet, &an_observer);
  ck_assert_uint_eq(counter, 0);
  
  planet_reduce_life(&a_planet, 0);
  ck_assert_uint_eq(counter, 0);
  planet_reduce_life(&a_planet, 0);
  ck_assert_uint_eq(counter, 0);
  
  a_planet.player_index = NULL_PLAYER_INDEX;
  planet_reduce_life(&a_planet, planet_get_life_max_unsafe(&a_planet));
  ck_assert_uint_eq(counter, 0);
  
  a_planet.player_index = NULL_PLAYER_INDEX == 0 ? 1 : 0;
  planet_reduce_life(&a_planet, planet_get_life_max_unsafe(&a_planet));
  ck_assert_uint_eq(counter, 0);
  
  position_2d_uint a_position = { 0, 0 };
  ship_t a_ship = ship_create_default(&a_position, &a_position);
  ship_set_power(&a_ship, planet_get_life_max_unsafe(&a_planet));
  a_ship.player_index   = NULL_PLAYER_INDEX;
  a_planet.player_index = NULL_PLAYER_INDEX;
  planet_collision_with_other_player_not_destroyed_ship(&a_planet, &a_ship);
  ck_assert_uint_eq(counter, 0);
  a_ship.player_index = NULL_PLAYER_INDEX == 0 ? 1 : 0;
  planet_collision_with_other_player_not_destroyed_ship(&a_planet, &a_ship);
  ck_assert_uint_eq(counter, 1);
  a_planet.player_index = NULL_PLAYER_INDEX;
  planet_collision_with_other_player_not_destroyed_ship(&a_planet, &a_ship);
  ck_assert_uint_eq(counter, 2);
  a_ship.player_index = (NULL_PLAYER_INDEX +1) % NB_MAX_PLAYERS;
  planet_collision_with_other_player_not_destroyed_ship(&a_planet, &a_ship);
  ck_assert_uint_eq(counter, 3);
  a_ship.player_index = (NULL_PLAYER_INDEX +2) % NB_MAX_PLAYERS;
  planet_collision_with_other_player_not_destroyed_ship(&a_planet, &a_ship);
  ck_assert_uint_eq(counter, 4);
  a_ship.player_index = (NULL_PLAYER_INDEX +3) % NB_MAX_PLAYERS;
  planet_collision_with_other_player_not_destroyed_ship(&a_planet, &a_ship);
  ck_assert_uint_eq(counter, 5);
  ship_destructor(&a_ship);
  ck_assert_uint_eq(counter, 5);
}
END_TEST

START_TEST(test_planet_event_on_ship_creation)
{
  unsigned int counter = 0;
  
  an_observer.handler = planet_observer_on_ship_creation_counter_handler;
  an_observer.data = &counter;
  an_observer.data_free = NULL;
  planet_add_observer(&a_planet, &an_observer);
  ck_assert_uint_eq(counter, 0);
  
  struct universe_t a_universe;
  universe_init_first_time_only(&a_universe);
  planet_produce_force(&a_planet, NULL);
  ck_assert_uint_eq(counter, 1);
  planet_produce_force(&a_planet, &a_universe);
  ck_assert_uint_eq(counter, 2);
  universe_destructor(&a_universe);
  ck_assert_uint_eq(counter, 2);
}
END_TEST


Suite*
planet_observers_suite(void)
{
  Suite* s = suite_create("Observers of planet");
  
  TCase* tc_planet_observers_base = tcase_create("Base");
  tcase_add_checked_fixture(tc_planet_observers_base,
			    tests_setup_init, tests_setup_free);
  tcase_add_test(tc_planet_observers_base,
		 test_planet_add_observer);
  tcase_add_test(tc_planet_observers_base,
		 test_planet_remove_observers);
  tcase_add_test(tc_planet_observers_base,
		 test_planet_notify_observers);
  suite_add_tcase(s, tc_planet_observers_base);
  
  TCase* tc_planet_observers_events = tcase_create("Events");
  tcase_add_checked_fixture(tc_planet_observers_events,
			    tests_setup_init, tests_setup_free);
  tcase_add_test(tc_planet_observers_events,
		 test_planet_event_on_life_change);
  tcase_add_test(tc_planet_observers_events,
		 test_planet_event_on_player_change);
  tcase_add_test(tc_planet_observers_events,
		 test_planet_event_on_ship_creation);
  suite_add_tcase(s, tc_planet_observers_events);
  
  return s;
}

int
main(void)
{
  size_t number_failed;
  Suite* s;
  SRunner* sr;
  
  s = planet_observers_suite();
  sr = srunner_create(s);
  
  srunner_run_all(sr, CK_NORMAL);
  number_failed = srunner_ntests_failed(sr);
  srunner_free(sr);
  
  return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
}
